#pragma once
#include <d2d1.h>
#include <shellapi.h>
#include "Events.h"
#include "List.h"
#include "D2d/Colors.h"
#include "D2d/D2DGraphics.h"

#pragma comment(lib, "Imm32.lib")

enum class ImageSizeMode : char
{
	ImageSizeMode_Normal,
	ImageSizeMode_CenterImage,
	ImageSizeMode_StretchIamge,
	ImageSizeMode_Zoom
};
enum class UIClass : int
{
	UI_Base,
	UI_Form,
	UI_Label,
	UI_Button,
	UI_PictureBox,
	UI_TextBox,
	UI_RichTextBox,
	UI_PasswordBox,
	UI_ComboBox,
	UI_GridView,
	UI_CheckBox,
	UI_RadioBox,
	UI_ProgressBar,
	UI_TreeView,
	UI_Panel,
	UI_TabPage,
	UI_TabControl,
	UI_Switch
};
class Control
{
private:
	POINT _location = {0, 0};
	SIZE _size = {120, 20};
	bool _visible = true;
	D2D1_COLOR_F _backcolor = Colors::gray91;
	D2D1_COLOR_F _forecolor = Colors::Black;
	D2D1_COLOR_F _boldercolor = Colors::Black;
	ID2D1Bitmap *_image = NULL;
	std::wstring _text;
	List<Control *> Children;

public:
	HWND Handle = NULL;
	Control *MostParent = NULL;
	Control *UnderMouse = NULL;
	Control *Selected = NULL;
	D2DGraphics *Render;
	virtual UIClass Type() { return UIClass::UI_Base; }
	D2dFont *Font = NULL;
	bool Enable = true;
	bool Checked = false;
	ImageSizeMode SizeMode;
	Control *Parent = NULL;
	OnPaintEvent OnPaint;
	MouseWheelEvent OnMouseWheel;
	MouseMoveEvent OnMouseMove;
	MouseUpEvent OnMouseUp;
	MouseDownEvent OnMouseDown;
	MouseDoubleEvent OnMouseDoubleClick;
	MouseClickEvent OnMouseClick;
	KeyUpEvent OnKeyUp;
	KeyDownEvent OnKeyDown;
	OnCloseEvent OnClose;
	SizeChangedEvent SizeChanged;
	MovedEvent Moved;
	OnCharInputEvent OnCharInput;
	OnCheckedEvent OnChecked;
	TextChangedEvent TextChanged;
	DropFileEvent OnDropFile;
	__int64 Tag = NULL;
	List<Control *> ForeGroundControls;
	Control()
		: Enable(true),
		  _visible(true),
		  Checked(false),
		  Parent(nullptr),
		  Font(nullptr),
		  Tag(NULL),
		  SizeMode(ImageSizeMode::ImageSizeMode_Zoom),
		  MostParent(NULL),
		  _image(NULL),
		  _text(L"")
	{
	}
	~Control()
	{
		if (this->Font)
		{
			delete this->Font;
		}
		for (auto c : this->Children)
		{
			delete c;
		}
	}
	virtual bool IsContainer() { return false; }
	virtual void Update() {}
	virtual void SingleUpdate()
	{
		if (this->IsVisual == false)
			return;
		this->Render->BeginRender();
		this->Update();
		for (auto fc : this->MostParent->ForeGroundControls)
		{
			fc->Update();
		}
		this->Render->EndRender();
	}
	READONLY_PROPERTY(int, Count);
	GET(int, Count)
	{
		return this->Children.Count;
	}
	Control *operator[](int index)
	{
		return this->Children[index];
	}
	Control *get(int index)
	{
		return this->Children[index];
	}
	Control *AddControl(Control *c)
	{
		if (c->Parent)
		{
			throw "This control already belongs to another container!";
			return NULL;
		}
		if (this->Children.Contains(c))
		{
			return c;
		}
		this->Children.Add(c);
		c->Parent = this;
		c->MostParent = this->MostParent;
		c->Render = this->Render;
		return c;
	}
	D2D1_POINT_2F LastChildLR()
	{
		if (this->Children.Count)
		{
			auto last = this->Children.Last();
			auto loc = last->Location;
			auto siz = last->ActualSize();
			return D2D1_POINT_2F{(float)loc.x + siz.cy, (float)loc.y + siz.cy};
		}
		return {0, 0};
	}
	D2D1_POINT_2F MaxChildLR()
	{
		D2D1_POINT_2F result = {0, 0};
		if (this->Children.Count)
		{
			for (auto c : this->Children)
			{
				auto last = this->Children.Last();
				auto loc = last->Location;
				auto siz = last->ActualSize();
				auto tmp = D2D1_POINT_2F{(float)loc.x + siz.cy, (float)loc.y + siz.cy};
				if (tmp.x > result.x)
					result.x = tmp.x;
				if (tmp.y > result.y)
					result.y = tmp.y;
			}
		}
		return result;
	}
	void RemoveControl(Control *c)
	{
		this->Children.Remove(c);
		c->Parent = NULL;
		c->MostParent = NULL;
		c->Render = NULL;
	}
	READONLY_PROPERTY(POINT, AbsLocation);
	GET(POINT, AbsLocation)
	{
		Control *tmpc = this;
		POINT tmpl = {0, 0};
		if (tmpc->Type() == UIClass::UI_Form)
			return tmpl;
		while (true)
		{
			auto loc = tmpc->Location;
			tmpl.x += loc.x;
			tmpl.y += loc.y;
			tmpc = tmpc->Parent;
			if (tmpc->Type() == UIClass::UI_Form)
			{
				break;
			}
		}
		return tmpl;
	}

	PROPERTY(D2D1_RECT_F, AbsRect);
	GET(D2D1_RECT_F, AbsRect)
	{
		Control *tmpc = this;
		auto absMin = this->AbsLocation;
		auto absMax = POINT{absMin.x + this->ActualSize().cx, absMin.y + this->ActualSize().cy};
		if (tmpc->Type() == UIClass::UI_Form)
			return D2D1_RECT_F{(float)absMin.x, (float)absMin.y, (float)absMax.x, (float)absMax.y};

		while (true)
		{
			auto _absMin = tmpc->AbsLocation;
			auto _absMax = POINT{_absMin.x + tmpc->ActualSize().cx, _absMin.y + tmpc->ActualSize().cy};
			auto rec = D2D1_RECT_F{(float)_absMin.x, (float)_absMin.y, (float)_absMax.x, (float)_absMax.y};
			if (rec.left > absMin.x)
				absMin.x = rec.left;
			if (rec.top > absMin.y)
				absMin.y = rec.top;
			if (rec.right < absMax.x)
				absMax.x = rec.right;
			if (rec.bottom < absMax.y)
				absMax.y = rec.bottom;
			tmpc = tmpc->Parent;
			if (tmpc->Type() == UIClass::UI_Form)
			{
				break;
			}
		}

		return D2D1_RECT_F{(float)absMin.x, (float)absMin.y, (float)absMax.x, (float)absMax.y};
	}

	PROPERTY(bool, IsVisual);
	GET(bool, IsVisual)
	{
		if (this->Visable == false)
			return false;
		Control *tmpc = this;
		while (true)
		{
			if (tmpc->Visable == false)
				return false;
			tmpc = tmpc->Parent;
			if (tmpc->Type() == UIClass::UI_Form)
			{
				break;
			}
		}
		return true;
	}
	PROPERTY(bool, Visable);
	GET(bool, Visable)
	{
		return _visible;
	}
	SET(bool, Visable)
	{
		if (_visible != value)
		{
			_visible = value;
			if (this->Type() != UIClass::UI_Form && this->Parent && this->Parent->IsVisual == false)
				return;
			if (this->MostParent)
				this->MostParent->Update();
		}
	}
	PROPERTY(POINT, Location);
	GET(POINT, Location)
	{
		return _location;
	}
	SET(POINT, Location)
	{
		this->Moved(this);
		_location = value;
	}
	PROPERTY(SIZE, Size);
	GET(SIZE, Size)
	{
		return _size;
	}
	SET(SIZE, Size)
	{
		this->SizeChanged(this);
		_size = value;
	}

	READONLY_PROPERTY(float, Right);
	GET(float, Right)
	{
		return this->Left + this->Width;
	}
	READONLY_PROPERTY(float, Bottom);
	GET(float, Bottom)
	{
		return this->Top + this->Height;
	}
	PROPERTY(std::wstring, Text);
	GET(std::wstring, Text)
	{
		return _text;
	}
	SET(std::wstring, Text)
	{
		if (value != _text)
			this->TextChanged(this, _text, value);
		_text = value;
	}
	PROPERTY(ID2D1Bitmap *, Image);
	GET(ID2D1Bitmap *, Image)
	{
		return _image;
	}
	SET(ID2D1Bitmap *, Image)
	{
		_image = value;
	}
	PROPERTY(D2D1_COLOR_F, BolderColor);
	GET(D2D1_COLOR_F, BolderColor)
	{
		return _boldercolor;
	}
	SET(D2D1_COLOR_F, BolderColor)
	{
		_boldercolor = value;
	}
	PROPERTY(D2D1_COLOR_F, BackColor);
	GET(D2D1_COLOR_F, BackColor)
	{
		return _backcolor;
	}
	SET(D2D1_COLOR_F, BackColor)
	{
		_backcolor = value;
	}
	PROPERTY(D2D1_COLOR_F, ForeColor);
	GET(D2D1_COLOR_F, ForeColor)
	{
		return _forecolor;
	}
	SET(D2D1_COLOR_F, ForeColor)
	{
		_forecolor = value;
	}

	PROPERTY(int, Left);
	GET(int, Left)
	{
		return this->Location.x;
	}
	SET(int, Left)
	{
		this->_location = POINT{value, this->_location.y};
	}
	PROPERTY(int, Top);
	GET(int, Top)
	{
		return this->Location.y;
	}
	SET(int, Top)
	{
		this->_location = POINT{this->_location.x, value};
	}
	PROPERTY(int, Width);
	GET(int, Width)
	{
		return this->_size.cx;
	}
	SET(int, Width)
	{
		this->SizeChanged(this);
		this->_size.cx = value;
	}
	PROPERTY(int, Height);
	GET(int, Height)
	{
		return this->_size.cy;
	}
	SET(int, Height)
	{
		this->SizeChanged(this);
		_size.cy = value;
	}
	virtual void RenderImage()
	{
		if (this->_image)
		{
			auto absLocation = this->AbsLocation;
			auto size = this->_image->GetSize();
			if (size.width > 0 && size.height > 0)
			{
				auto asize = this->ActualSize();
				switch (this->SizeMode)
				{
				case ImageSizeMode::ImageSizeMode_Normal:
				{
					this->Render->DrawBitmap(this->_image, absLocation.x, absLocation.y, size.width, size.height);
				}
				break;
				case ImageSizeMode::ImageSizeMode_CenterImage:
				{
					float xf = (asize.cx - size.width) / 2.0f;
					float yf = (asize.cy - size.height) / 2.0f;
					this->Render->DrawBitmap(this->_image, absLocation.x + xf, absLocation.y + yf, size.width, size.height);
				}
				break;
				case ImageSizeMode::ImageSizeMode_StretchIamge:
				{
					this->Render->DrawBitmap(this->_image, absLocation.x, absLocation.y, asize.cx, asize.cy);
				}
				break;
				case ImageSizeMode::ImageSizeMode_Zoom:
				{
					float xp = asize.cx / size.width;
					float yp = asize.cy / size.height;
					float tp = xp < yp ? xp : yp;
					float tw = size.width * tp;
					float th = size.height * tp;
					float xf = (asize.cx - tw) / 2.0f;
					float yf = (asize.cy - th) / 2.0f;
					this->Render->DrawBitmap(this->_image, absLocation.x + xf, absLocation.y + yf, tw, th);
				}
				break;
				default:
					break;
				}
			}
		}
	}
	virtual SIZE ActualSize()
	{
		return this->Size;
	}
	virtual bool ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam, int xof, int yof)
	{
		if (WM_LBUTTONDOWN == message)
		{
			if (this->MostParent->Selected && this->MostParent->Selected != this)
			{
				auto se = this->MostParent->Selected;
				this->MostParent->Selected = this;
				se->SingleUpdate();
			}
		}
		switch (message)
		{
		case WM_DROPFILES:
		{
			HDROP hDropInfo = HDROP(wParam);
			UINT uFileNum = DragQueryFile(hDropInfo, 0xffffffff, NULL, 0);
			TCHAR strFileName[MAX_PATH];
			List<std::wstring> files;
			for (int i = 0; i < uFileNum; i++)
			{
				DragQueryFile(hDropInfo, i, strFileName, MAX_PATH);
				files.Add(strFileName);
			}
			DragFinish(hDropInfo);
			if (files.Count > 0)
			{
				this->OnDropFile(this, files);
			}
		}
		break;
		case WM_MOUSEWHEEL: // mouse wheel
		{
			MouseEventArgs event_obj = MouseEventArgs(MouseButtons::MouseButtons_None, 0, xof, yof, GET_WHEEL_DELTA_WPARAM(wParam));
			this->OnMouseWheel(this, event_obj);
		}
		break;
		case WM_MOUSEMOVE: // mouse move
		{
			MouseEventArgs event_obj = MouseEventArgs(MouseButtons::MouseButtons_None, 0, xof, yof, HIWORD(wParam));
			this->OnMouseMove(this, event_obj);
		}
		break;
		case WM_LBUTTONDOWN: // mouse down
		case WM_RBUTTONDOWN:
		case WM_MBUTTONDOWN:
		{
			if (WM_LBUTTONDOWN == message)
			{
				this->MostParent->Selected = this;
			}
			MouseEventArgs event_obj = MouseEventArgs(FromParamToMouseButtons(message), 0, xof, yof, HIWORD(wParam));
			this->OnMouseDown(this, event_obj);
			this->Update();
		}
		break;
		case WM_LBUTTONUP: // mouse up
		case WM_RBUTTONUP:
		case WM_MBUTTONUP:
		{
			MouseEventArgs event_obj = MouseEventArgs(FromParamToMouseButtons(message), 0, xof, yof, HIWORD(wParam));
			this->OnMouseUp(this, event_obj);
			this->Update();
		}
		break;
		case WM_LBUTTONDBLCLK: // mouse double click
		{
			MouseEventArgs event_obj = MouseEventArgs(FromParamToMouseButtons(message), 0, xof, yof, HIWORD(wParam));
			this->OnMouseDoubleClick(this, event_obj);
		}
		break;
		case WM_KEYDOWN: // keyboard down
		{
			KeyEventArgs event_obj = KeyEventArgs((Keys)(wParam | 0));
			this->OnKeyDown(this, event_obj);
		}
		break;
		case WM_KEYUP: // keyboard up
		{
			KeyEventArgs event_obj = KeyEventArgs((Keys)(wParam | 0));
			this->OnKeyUp(this, event_obj);
		}
		break;
		}
		return true;
	}
};